package com.tangzx.builder.decoder;

import macromedia.abc.BytecodeBuffer;
import macromedia.abc.DecoderException;
import macromedia.asc.embedding.avmplus.ActionBlockConstants;
import macromedia.asc.util.Decimal128;
import com.tangzx.builder.abc.AbcCPool;
import com.tangzx.builder.abc.AbcFile;
import com.tangzx.builder.abc.ClassInfo;
import com.tangzx.builder.abc.ExceptionInfo;
import com.tangzx.builder.abc.InstanceInfo;
import com.tangzx.builder.abc.Metadata;
import com.tangzx.builder.abc.Method;
import com.tangzx.builder.abc.MethodBody;
import com.tangzx.builder.abc.ScriptInfo;
import com.tangzx.builder.abc.Trait;
import com.tangzx.builder.abc.string.*;
import com.tangzx.builder.context.IAbcContext;
import com.tangzx.builder.visitor.ABCVisitor;
import flash.swf.tags.DoABC;

public class AbcDecoder implements IABCDecoder{

	protected BytecodeBuffer buf;
	protected AbcFile abcFile;
	protected AbcCPool cpool;
	protected IAbcContext abcContext;
	protected DoABC abc;
	
	public AbcDecoder() {
	}

	public IAbcContext getAbcContext() {
		return abcContext;
	}

	public void setAbcContext(IAbcContext abcContext) {
		this.abcContext = abcContext;
	}
	
	public AbcCPool getCPool() {
		return abcFile.cpool;
	}
	
	public AbcFile readAbc(DoABC abc) {
		this.abc = abc;
		buf = new BytecodeBuffer(abc.abc);
		abcFile = new AbcFile();
		abcFile.data = abc;
		abcFile.minorVersion = buf.readU16();
        abcFile.majorVersion = buf.readU16();
        startParse();
        return abcFile;
	}
	
	public void startParse()
	{
		scanCpool(buf);
        scanMethods(buf);
        scanMetadata(buf);
        scanClasses(buf);

        int pos = buf.pos();
        byte[] bytes = buf.readBytes(buf.size() - buf.pos());
        buf.seek(pos);
        abcFile.remain = bytes;
        
        scanScripts(buf);
        scanMethodBodys(buf);
	}
	
	protected void readInts(BytecodeBuffer buf) {
		int size = buf.readU32();   
        int[] ints = new int[size];
        for (int i = 1; i < size; i++)
        {
            ints[i] = buf.readU32();
        }
        cpool.setIntSet(ints);
	}
	
	protected void readUints(BytecodeBuffer buf) {
		int size = buf.readU32();
        long[] uints = new long[size];
        for (int i = 1; i < size; i++)
        {
            uints[i] = buf.readU32() & 0xFFFFFFFFL;
        }
        cpool.setUintSet(uints);
	}
	
	protected void readDoubles(BytecodeBuffer buf) {
		int size = buf.readU32();
        double[] doubles = new double[size];
        for (int i = 1; i < size; i++)
        {
            doubles[i] = buf.readDouble();
        }
        cpool.setDoubleSet(doubles);
	}
	
	
	
	protected void readDecimal(BytecodeBuffer buf) {
		if (abcFile.minorVersion >= ActionBlockConstants.MINORwithDECIMAL)
        {
            int size = buf.readU32();
            Decimal128[] decimals = new Decimal128[size];

            for (int i = 1; i < size; i++)
            {
               byte[] rep = buf.readBytes(16);
               decimals[i] = new Decimal128(rep);
            }
            cpool.setDecimalSet(decimals);
        }
	}
	
	protected void readStrings(BytecodeBuffer buf) {
		int size = buf.readU32();
        AbcString[] strings = new AbcString[size];
        strings[0] = AbcString.get("".intern());
        for (int i = 1; i < size; i++)
        {
            int length = buf.readU32();
            String value = buf.readString(length).intern();
            strings[i] = abcContext.getString(value);
            buf.skip(length);
        }
        cpool.setStringSet(strings);
	}
	
	
	protected void readNS(BytecodeBuffer buf) {
		int size = buf.readU32();
        Namespace[] namespaces = new Namespace[size];
        for (int i = 1; i < size; i++)
        {
            int ns_kind = buf.readU8(); // kind byte
            int name_offset = buf.readU32();
            Namespace ns = new Namespace(name_offset, ns_kind);
            ns.setAbcFile(abcFile);
            ns.setCpool(cpool);
            namespaces[i] = ns;
        }
        cpool.setNamespaceSet(namespaces);
	}
	
	protected void readNSS(BytecodeBuffer buf) {
		int size = buf.readU32();
        NamespaceSet[] namespaceSets = new NamespaceSet[size];
        for (int i = 1; i < size; i++)
        {
            int count = buf.readU32(); // count
            int[] ns_ids = new int[count];
            for(int q =0; q < count; ++q)
            {
                ns_ids[q] = buf.readU32();
            }
            NamespaceSet nss = new NamespaceSet(ns_ids);
            nss.setCpool(cpool);
            namespaceSets[i] = nss;
        }
        cpool.setNamespaceSetSet(namespaceSets);
	}
	
	protected void readMN(BytecodeBuffer buf) {
		int size = buf.readU32();
        MultiName[] nameData = new MultiName[size];
        
        for (int i = 1; i < size; i++)
        {
            int kind = buf.readU8();
            switch (kind)
            {
                case ActionBlockConstants.CONSTANT_Qname:
                case ActionBlockConstants.CONSTANT_QnameA:
                    nameData[i] = new QName(kind, new int[] {buf.readU32(), buf.readU32()});
                    break;
                case ActionBlockConstants.CONSTANT_RTQname:
                case ActionBlockConstants.CONSTANT_RTQnameA:
                	nameData[i] = new RTQName(kind, new int[] {buf.readU32()});
                    break;
                case ActionBlockConstants.CONSTANT_RTQnameL:
                case ActionBlockConstants.CONSTANT_RTQnameLA:
                    nameData[i] = new RTQNameL(kind);
                    break;
                case ActionBlockConstants.CONSTANT_Multiname:
                case ActionBlockConstants.CONSTANT_MultinameA:
                    nameData[i] = new MultiName(kind, new int[] {buf.readU32(), buf.readU32()} );
                    break;
                case ActionBlockConstants.CONSTANT_MultinameL:
                case ActionBlockConstants.CONSTANT_MultinameLA:
                	nameData[i] = new MultiName(kind, new int[] {buf.readU32()});
                    break;
                case ActionBlockConstants.CONSTANT_TypeName:
                    int name_index = buf.readU32(); // name index
                    int count = buf.readU32(); // param count;
                    int[] entries = new int[count+2];
                    entries[0] = name_index;
                    entries[1] = count;
                    for ( int k = 0; k < count; k++)
                        entries[k+2] = buf.readU32();
                    nameData[i] = new MultiName(kind, entries);
                    break;
                default:
                    throw new RuntimeException("bad multiname type: " + kind);

            }
        }
        cpool.setMultiNameSet(nameData);
	}
	
	public void scanCpool(BytecodeBuffer buf)
    {
		cpool = new AbcCPool();
		abcFile.cpool = cpool;
		readInts(buf);
		readUints(buf);
		readDoubles(buf);
		readDecimal(buf);
        readStrings(buf);
        readNS(buf);
        readNSS(buf);
        readMN(buf);
    }
	
	
	
	public void scanMethods(BytecodeBuffer buf)
    {
        int methodEntries = buf.readU32();
        Method[] methods = new Method[methodEntries];
        abcFile.methods = methods;
        for (int i = 0; i < methodEntries; i++)
        {
            int param_count = buf.readU32();
            int return_type = buf.readU32();
            int[] param_types = new int[param_count];
            for ( int j = 0; j < param_count; j++ )
                param_types[j] = buf.readU32();
            int name_index = buf.readU32();
            int flags = buf.readU8();   
            
            int optional_param_count = ((flags & ActionBlockConstants.METHOD_HasOptional) != 0)? buf.readU32(): 0;
            int[] optional_param_types = new int[optional_param_count];
            int[] optional_param_kinds = new int[optional_param_count];
            
            for( int q = 0; q < optional_param_count; ++q )
            {
                optional_param_types[q] = buf.readU32(); 
                optional_param_kinds[q] = buf.readU8();
            }

            int param_name_count = ((flags & ActionBlockConstants.METHOD_HasParamNames)!=0) ? param_count : 0;
            int[] param_names = new int[param_name_count];
            for( int q = 0; q < param_name_count; ++q )
            {
                param_names[q] = buf.readU32();
            }
            
            Method method = new Method(return_type, param_types, name_index, flags, optional_param_types, optional_param_kinds, param_names);
            method.setCpool(cpool);
            methods[i] = method;
        }
    }
	
	public void scanMetadata(BytecodeBuffer buf)
    {
        int metadataEntries = buf.readU32();
        
        Metadata[] raw_metadata = new Metadata[metadataEntries];
        //this.semantic_metadata = new MetaData[metadataEntries];
        abcFile.metadata = raw_metadata;
        
        for (int i = 0; i < metadataEntries; i++)
        {
            int name_index  = buf.readU32();
            cpool.getString(name_index).vo.setCanNotMix();
            int value_count = buf.readU32(); //returnType
            
            int[] keys   = new int[value_count];
            int[] values = new int[value_count];
            
            for ( int j = 0; j < value_count; j++ )
            {
                keys[j] = buf.readU32();
                cpool.getString(keys[j]).vo.setCanNotMix();
            }
            for(int j = 0; j < value_count; j++ )
            {
                values[j] = buf.readU32();
                cpool.getString(values[j]).vo.setCanNotMix();
            }
            Metadata meta = new Metadata(name_index, keys, values);
            meta.setCpool(cpool);
            raw_metadata[i] = meta;
        }
    }

    public void scanClasses(BytecodeBuffer buf)
    {
        int classEntries = buf.readU32();

        ClassInfo[] classInfos = new ClassInfo[classEntries];
        InstanceInfo[] instanceInfos = new InstanceInfo[classEntries];
        abcFile.classInfos = classInfos;
        abcFile.instanceInfos = instanceInfos;
        // InstanceInfos
        for(int i = 0; i < classEntries; ++i)
        {
            int name_index = buf.readU32();
            int super_index = buf.readU32(); //super_index
            
            int flags = buf.readU8();
            int protected_ns = 0;
            if ((flags & ActionBlockConstants.CLASS_FLAG_protected) != 0)
            {
                protected_ns = buf.readU32();
            }
               
            int interface_count = buf.readU32();
            int[] interfaces = new int[interface_count];
            for ( int j = 0; j < interface_count; j++ )
                interfaces[j] = buf.readU32();
            int init_index = buf.readU32();
            Trait[] itraits = scanTraits(buf);
            InstanceInfo instance = new InstanceInfo(name_index, super_index, flags, protected_ns, interfaces, init_index, itraits);
            instance.setAbcFile(abcFile);
            instanceInfos[i] = instance;
        }

        // ClassInfos
        for(int i = 0; i < classEntries; ++i)
        {
            int clinit_index = buf.readU32();
            Trait[] ctraits = scanTraits(buf);

            classInfos[i] = new ClassInfo(clinit_index, ctraits);
        }
    }
    
    public Trait[] scanTraits(BytecodeBuffer buf)
    {
        int count = buf.readU32();
        
        Trait[] result = new Trait[count];

        for (int i = 0; i < count; i++)
        {
            int name_index = buf.readU32();
            int kind = buf.readU8();
            int tag = kind & 0x0f;
            
            Trait new_trait = null;
            
            switch (tag)
            {
            case ActionBlockConstants.TRAIT_Var:
            case ActionBlockConstants.TRAIT_Const:
            {
                int slot_id = buf.readU32();
                int trait_type = buf.readU32();
                int value_index = buf.readU32();
                int value_kind = 0;
                if( value_index > 0 )
                    value_kind = buf.readU8(); 
            
                new_trait = new Trait(name_index, kind, new int[] { slot_id, trait_type, value_index, value_kind});
                break;
            }
            case ActionBlockConstants.TRAIT_Method:
            case ActionBlockConstants.TRAIT_Getter:
            case ActionBlockConstants.TRAIT_Setter:
            {
                int disp_id = buf.readU32();
                int method = buf.readU32();
                new_trait = new Trait(name_index, kind, new int[] {disp_id, method} );
                break;
            }
            case ActionBlockConstants.TRAIT_Class:
            case ActionBlockConstants.TRAIT_Function:
            {
                int slot_id  = buf.readU32();
                int class_id = buf.readU32();
                new_trait = new Trait(name_index, kind, new int[] { slot_id, class_id } );
                break;
            }
            default:
                break;
            }
            
            assert(new_trait != null);
            new_trait.setCpool(cpool);
            new_trait.setAbcFile(abcFile);
            result[i] = new_trait;
            
            if(new_trait.hasMetadata())
            {
                int metadata_count = buf.readU32();
                int[] metadata = new int[metadata_count];
                for ( int j = 0; j < metadata_count; j++)
                    metadata[j] = buf.readU32();
                new_trait.addMetadata(metadata);
            }
        }
        
        return result;
    }

    public void scanScripts(BytecodeBuffer buf)
    {
        int script_count = buf.readU32();
        ScriptInfo[] scriptInfos = new ScriptInfo[script_count];
        abcFile.scriptInfos = scriptInfos;
        for(int i = 0 ; i < script_count; ++i )
        { 
            scriptInfos[i] = new ScriptInfo(buf.readU32(), scanTraits(buf));
            scriptInfos[i].setCpool(cpool);
        }
    }
    
    /**
     * 	u30 method 
		u30 max_stack 
		u30 local_count 
		u30 init_scope_depth  
		u30 max_scope_depth 
		u30 code_length 
		u8  code[code_length] 
		u30 exception_count 
		exception_info exception[exception_count] 
		u30 trait_count 
		traits_info trait[trait_count]
		
		exception_info  
		{ 
			u30 from 
			u30 to  
			u30 target 
			u30 exc_type 
			u30 var_name 
		} 
     * @param buf
     */
    public void scanMethodBodys(BytecodeBuffer buf)
    {
    	int count = buf.readU32();
    	MethodBody[] methodBodys = new MethodBody[count];
    	for (int i = 0; i < count; i++)
    	{
        	int mIndex = buf.readU32();
        	Method method = abcFile.methods[mIndex];
        	//System.out.println("MMM:" + method);
        	int max_stack = buf.readU32();
        	int local_count = buf.readU32();
        	int init_scope_depth = buf.readU32();
        	int max_scope_depth = buf.readU32();
        	int code_length = buf.readU32();
        	byte[] code = buf.readBytes(code_length);
        	//Exception:
        	int expCount = buf.readU32();
        	ExceptionInfo[] exceptions = new ExceptionInfo[expCount];
        	for (int j = 0; j < expCount; j++)
        	{
        		int from = buf.readU32();
        		int to = buf.readU32();
        		int target = buf.readU32();
        		int exc_type = buf.readU32();
        		int var_name = buf.readU32();
        		ExceptionInfo ei = new ExceptionInfo(from, to, target, exc_type, var_name);
        		exceptions[j] = ei;
        	}
        	Trait[] traits = scanTraits(buf);
        	
        	MethodBody methodBody = new MethodBody(mIndex, max_stack, local_count, init_scope_depth, max_scope_depth, code, exceptions, traits);
        	methodBodys[i] = methodBody;
        	parse(code);
        	methodBody.setAbcFile(abcFile);
    	}
    }
    
    private void parse(byte[] code) {
    	BytecodeBuffer buf = new BytecodeBuffer(code);
		Opcodes opcode = new Opcodes(buf);
		try {
			opcode.decode(0, buf.size(), new ABCVisitor(cpool));
		} catch (DecoderException e) {
			e.printStackTrace();
		}
    }
}